﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Text;
using System.Xml;
using System.Device.Location;
using System.Linq;

namespace GFService
{
    public enum NotificationType
    {
        Tile = 1,
        Toast = 2,
        Raw = 3
    }

    public enum BatchingInterval
    {
        TileImmediately = 1,
        ToastImmediately = 2,
        RawImmediately = 3,
        TileWait450 = 11,
        ToastWait450 = 12,
        RawWait450 = 13,
        TileWait900 = 21,
        ToastWait900 = 22,
        RawWait900 = 23
    }
    public class GeoFinder : IGeoFinder
    {

        GeoFinderContainer gf_c = new GeoFinderContainer();

        Dictionary<long, GeoFinderClient> allClients = new Dictionary<long, GeoFinderClient>();

        Dictionary<long, List<long>> clients_whitelist = new Dictionary<long, List<long>>();
        Dictionary<long, List<long>> clients_blacklist = new Dictionary<long, List<long>>();

        public GeoFinder()
        {

            foreach (var client in gf_c.GeoFinderClients)
            {
                allClients.Add(client.EmailHASH, client);
            }

            foreach (var client in gf_c.GeoFinderClients)
            {
                clients_whitelist.Add(client.EmailHASH, (from cl in client.ToWhite select cl.EmailHASH).ToList<long>());
            }

            foreach (var client in gf_c.GeoFinderClients)
            {
                clients_blacklist.Add(client.EmailHASH, (from cl in client.ToBlack select cl.EmailHASH).ToList<long>());
            }
        }


        public void Subscribe(long facebookID, string uri, long facebook_id)
        {
            var hash_mail = facebook_id;
            //var hash_mail = mail.GetHashCode();

            if (allClients.ContainsKey(hash_mail))
            {
                allClients[hash_mail].PushURI = uri;
                allClients[hash_mail].IsConnected = true;
            }
            else
            {
                var new_user = new GeoFinderClient();

                new_user.EmailHASH = hash_mail;
                new_user.PushURI = uri;
                new_user.FacebookID = facebook_id;
                new_user.IsConnected = true;

                new_user.RawEnabled = true;
                new_user.TileEnabled = true;
                new_user.ToastEnabled = true;
                new_user.IsPushEnable = false;

                new_user.PositionGPS = "45,47946;12,25604";
                new_user.SearchingArea = 500;


                allClients.Add(hash_mail, new_user);

                gf_c.AddObject("GeoFinderClients", new_user);
            }

            gf_c.SaveChanges();

        }

        public void SetBlackAndWhiteFacebookFriends(long facebookID, List<long> friendsFacebookID)
        {
            var hash_mail = facebookID;
            //var hash_mail = mail.GetHashCode();

            List<long> white =
                (from friend in friendsFacebookID
                 let visibility = (friend % 10) == 0
                 let id = (long)friend / 10
                 where visibility
                 select id).ToList<long>();

            List<long> black =
                (from friend in friendsFacebookID
                 let visibility = (friend % 10) != 0
                 let id = (long)friend / 10
                 where visibility
                 select id).ToList<long>();

            var my_white_list = clients_whitelist[hash_mail];
            var my_black_list = clients_blacklist[hash_mail];

            white.ForEach
                (f =>
                {
                    if (!my_white_list.Contains(f))
                    {
                        my_white_list.Add(f);
                        my_black_list.Remove(f);

                        allClients[hash_mail].ToWhite.Add(allClients[f]);
                        allClients[hash_mail].ToBlack.Remove(allClients[f]);

                    }
                });
            black.ForEach
                    (f =>
                    {
                        if (!my_black_list.Contains(f))
                        {
                            my_black_list.Add(f);
                            my_white_list.Remove(f);

                            allClients[hash_mail].ToBlack.Add(allClients[f]);
                            allClients[hash_mail].ToWhite.Remove(allClients[f]);
                        }
                    });

            gf_c.SaveChanges();
        }
        public void SetBlackAndWhiteFacebookFriends_string(long facebookID, string friendsFacebookID)
        {
            var hash_mail = facebookID;
            //var hash_mail = mail.GetHashCode();
            var friends = friendsFacebookID.Split('-').ToList<string>();
            friends.RemoveAll(f => f == "");

            List<long> white =
                (from friend in friends
                 let visibility = (long.Parse(friend) % 10) == 0
                 let id = long.Parse(friend) / 10
                 where visibility
                 select id).ToList<long>();

            List<long> black =
                (from friend in friends
                 let visibility = (long.Parse(friend) % 10) != 0
                 let id = long.Parse(friend) / 10
                 where visibility
                 select id).ToList<long>();

            var my_white_list = clients_whitelist[hash_mail];
            var my_black_list = clients_blacklist[hash_mail];

            white.ForEach
                (f =>
                {
                    if (!my_white_list.Contains(f))
                    {
                        my_white_list.Add(f);
                        my_black_list.Remove(f);

                        allClients[hash_mail].ToWhite.Add(allClients[f]);
                        allClients[hash_mail].ToBlack.Remove(allClients[f]);

                    }
                });
            black.ForEach
                    (f =>
                    {
                        if (!my_black_list.Contains(f))
                        {
                            my_black_list.Add(f);
                            my_white_list.Remove(f);

                            allClients[hash_mail].ToBlack.Add(allClients[f]);
                            allClients[hash_mail].ToWhite.Remove(allClients[f]);
                        }
                    });

            gf_c.SaveChanges();
        }
        public List<long> UpdateFacebookFriends(long facebookID, List<long> friendsFacebookID)
        {
            var hash_mail = facebookID;
            var me = allClients[hash_mail];
            friendsFacebookID.ForEach
                    (f =>
                    {
                        if (allClients.ContainsKey(f) &&
                            !me.ToWhite.Contains(allClients[f]) &&
                            !me.ToBlack.Contains(allClients[f]))
                        {
                            //me.FromWhite.Add(me);
                            me.ToWhite.Add(allClients[f]);
                        }
                    });

            clients_whitelist[hash_mail] = clients_whitelist[hash_mail].Where(f => friendsFacebookID.Contains(f)).ToList<long>();
            clients_blacklist[hash_mail] = clients_blacklist[hash_mail].Where(f => friendsFacebookID.Contains(f)).ToList<long>();

            gf_c.SaveChanges();

            var return_list =
                (from friend in clients_blacklist[hash_mail]
                 let tmp = friend * 10 + 1
                 select tmp).ToList<long>();
            return_list =
                return_list.Concat<long>(
                    (from friend in clients_whitelist[hash_mail]
                     let tmp = friend * 10
                     select tmp).ToList<long>()).ToList<long>();

            return return_list;
        }

        public string UpdateFacebookFriends_string_friends(long facebookID, string friendsFacebookID)
        {
            var hash_mail = facebookID;
            var me = allClients[hash_mail];
            var friends = friendsFacebookID.Split('-').ToList<string>();
            friends.ForEach
                    (f =>
                    {
                        if (f != "" &&
                            allClients.ContainsKey(long.Parse(f)) &&
                            !me.ToWhite.Contains(allClients[long.Parse(f)]) &&
                            !me.ToBlack.Contains(allClients[long.Parse(f)]))
                        {
                            //me.FromWhite.Add(me);
                            me.ToWhite.Add(allClients[long.Parse(f)]);
                        }
                    });

            clients_whitelist[hash_mail] = clients_whitelist[hash_mail].Where(f => friends.Contains(f.ToString())).ToList<long>();
            clients_blacklist[hash_mail] = clients_blacklist[hash_mail].Where(f => friends.Contains(f.ToString())).ToList<long>();

            gf_c.SaveChanges();

            var result = "";
            var return_list =
                (from friend in clients_blacklist[hash_mail]
                 let tmp = friend * 10 + 1
                 select tmp).ToList<long>();

            return_list =
                return_list.Concat<long>(
                    (from friend in clients_whitelist[hash_mail]
                     let tmp = friend * 10
                     select tmp).ToList<long>()).ToList<long>();

            foreach (var elem in return_list)
            {
                result += (elem.ToString() + "-");
            }
            return result;
        }

        public void UpdatePushSettings(long facebookID, bool push_status, bool toast, bool tile, bool raw)
        {
            var hash_mail = facebookID;
            //var hash_mail = mail.GetHashCode();

            var me = allClients[hash_mail];

            me.IsPushEnable = push_status;
            me.RawEnabled = raw;
            me.TileEnabled = tile;
            me.ToastEnabled = toast;

            gf_c.SaveChanges();
        }

        public List<string> UpdateGPS(long facebookID, string gpsPosition)
        {
            var hash_mail = facebookID;
            //var hash_mail = mail.GetHashCode();

            var me = allClients[hash_mail];

            me.PositionGPS = gpsPosition;

            gf_c.SaveChanges();

            var my_white_friends = clients_whitelist[facebookID];
            var my_black_friends = clients_blacklist[facebookID];

            var closest_white =
                (from friend in my_white_friends
                 //let to_pos = toGeoCoord(allClients[friend].PositionGPS)
                 where !clients_blacklist[friend].Contains(me.EmailHASH)
                 select allClients[friend].FacebookID + "#" + allClients[friend].PositionGPS).ToList<string>();

            var closest_black =
                (from friend in my_black_friends
                 //let to_pos = toGeoCoord(allClients[friend].PositionGPS)
                 where !clients_blacklist[friend].Contains(me.EmailHASH)
                 select allClients[friend].FacebookID + "#" + allClients[friend].PositionGPS).ToList<string>();


            return closest_white.Concat(closest_black).ToList();
        }

        public string UpdateGPS_string(long facebookID, string gpsPosition)
        {
            var hash_mail = facebookID;
            //var hash_mail = mail.GetHashCode();

            var me = allClients[hash_mail];

            me.PositionGPS = gpsPosition;

            gf_c.SaveChanges();

            var my_white_friends = clients_whitelist[facebookID];
            var my_black_friends = clients_blacklist[facebookID];

            var closest_white =
                (from friend in my_white_friends
                 //let to_pos = toGeoCoord(allClients[friend].PositionGPS)
                 where !clients_blacklist[friend].Contains(me.EmailHASH)
                 select allClients[friend].FacebookID + "#" + allClients[friend].PositionGPS).ToList<string>();

            var closest_black =
                (from friend in my_black_friends
                 //let to_pos = toGeoCoord(allClients[friend].PositionGPS)
                 where !clients_blacklist[friend].Contains(me.EmailHASH)
                 select allClients[friend].FacebookID + "#" + allClients[friend].PositionGPS).ToList<string>();


            var result = "";
            var return_list = closest_white.Concat(closest_black).ToList();

            if (return_list.Capacity == 0)
                return "-";

            foreach (var elem in return_list)
            {
                result += (elem.ToString() + "-");
            }
            return result;
        }

        //private string toSQLString(GeoCoordinate coord)
        //{
        //    return coord.Latitude + ";" + coord.Longitude;
        //}

        private GeoCoordinate toGeoCoord(string coord)
        {

            var tmp = coord.Split(new char[] { ';' });
            var lat = double.Parse(tmp[0]);
            var lon = double.Parse(tmp[1]);
            return new GeoCoordinate(lat, lon);
        }

        public void UpdateSearchingArea(long facebookID, double searching_area)
        {
            allClients[facebookID].SearchingArea = (int)searching_area;
            gf_c.SaveChanges();
        }

        public void SendToastToAll(string title, string message)
        {
            var toastMessage = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
              "<wp:Notification xmlns:wp=\"WPNotification\">" +
                 "<wp:Toast>" +
                  "<wp:Text1>{0}</wp:Text1>" +
                  "<wp:Text2>{1}</wp:Text2>" +
                 "</wp:Toast>" +
              "</wp:Notification>";

            toastMessage = string.Format(toastMessage, title, message);

            var messageBytes = System.Text.Encoding.UTF8.GetBytes(toastMessage);

            foreach (var user in gf_c.GeoFinderClients)
            {
                if (user.ToastEnabled && user.IsConnected && user.IsPushEnable)
                {
                    SendMessage(new Uri(user.PushURI), messageBytes, NotificationType.Toast);
                }
            }

        }

        public void SendTileUpdateToAll(string title, int count, string imageUrl)
        {
            var tileMessage = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
              "<wp:Notification xmlns:wp=\"WPNotification\">" +
                 "<wp:Tile>" +
                  "<wp:BackgroundImage>{0}</wp:BackgroundImage>" +
                  "<wp:Count>{1}</wp:Count>" +
                  "<wp:Title>{2}</wp:Title>" +
                 "</wp:Tile> " +
              "</wp:Notification>";


            foreach (var user in gf_c.GeoFinderClients)
            {
                if (user.TileEnabled && user.IsConnected && user.IsPushEnable)
                {

                    tileMessage = string.Format(tileMessage, imageUrl, getClosestFriends(user.EmailHASH).Count, title);
                    var messageBytes = System.Text.Encoding.UTF8.GetBytes(tileMessage);

                    SendMessage(new Uri(user.PushURI), messageBytes, NotificationType.Tile);
                }
            }

        }

        public void SendRawNotificationToAll(string message)
        {
            var messageBytes = Encoding.UTF8.GetBytes(message);

            foreach (var user in gf_c.GeoFinderClients)
            {
                if (user.RawEnabled && user.IsConnected && user.IsPushEnable)
                {
                    SendMessage(new Uri(user.PushURI), messageBytes, NotificationType.Raw);
                }
            }

        }

        private List<GeoFinderClient> getClosestFriends(long facebookID)
        {
            var me = allClients[facebookID];
            var from_pos = toGeoCoord(me.PositionGPS);

            var my_white_friends = clients_whitelist[facebookID];
            var my_black_friends = clients_blacklist[facebookID];

            var closest_white =
                (from friend in my_white_friends
                 let to_pos = toGeoCoord(allClients[friend].PositionGPS)
                 where from_pos.GetDistanceTo(to_pos) <= me.SearchingArea &&
                       !clients_blacklist[friend].Contains(me.EmailHASH)
                 select allClients[friend]).ToList<GeoFinderClient>();

            var closest_black =
                (from friend in my_black_friends
                 let to_pos = toGeoCoord(allClients[friend].PositionGPS)
                 where from_pos.GetDistanceTo(to_pos) <= me.SearchingArea &&
                       !clients_blacklist[friend].Contains(me.EmailHASH)
                 select allClients[friend]).ToList<GeoFinderClient>();

            return closest_white.Concat(closest_black).ToList();
        }

        private static void SendMessage(Uri uri, byte[] message, NotificationType notificationType)
        {
            var request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = WebRequestMethods.Http.Post;
            request.ContentType = "text/xml";
            request.ContentLength = message.Length;


            request.Headers.Add("X-MessageID", Guid.NewGuid().ToString());

            switch (notificationType)
            {
                case NotificationType.Toast:
                    request.Headers["X-WindowsPhone-Target"] = "toast";
                    request.Headers.Add("X-NotificationClass", ((int)BatchingInterval.ToastImmediately).ToString());
                    break;
                case NotificationType.Tile:
                    request.Headers["X-WindowsPhone-Target"] = "token";
                    request.Headers.Add("X-NotificationClass", ((int)BatchingInterval.TileImmediately).ToString());
                    break;
                default:
                    request.Headers.Add("X-NotificationClass", ((int)BatchingInterval.RawImmediately).ToString());
                    break;
            }

            using (var requestStream = request.GetRequestStream())
            {
                requestStream.Write(message, 0, message.Length);
            }
            try
            {
                var response = (HttpWebResponse)request.GetResponse();

                var notificationStatus = response.Headers["X-NotificationStatus"];
                var subscriptionStatus = response.Headers["X-SubscriptionStatus"];
                var deviceConnectionStatus = response.Headers["X-DeviceConnectionStatus"];

                Debug.WriteLine(string.Format("Device Connection Status: {0}", deviceConnectionStatus));
                Debug.WriteLine(string.Format("Notification Status: {0}", notificationStatus));
                Debug.WriteLine(string.Format("Subscription Status: {0}", subscriptionStatus));
            }
            catch (WebException ex)
            {
                Debug.WriteLine(string.Format("ERROR: {0}", ex.Message));
            }
        }
    }
}
